package cn.geminis.lowcode.processor;

import cn.geminis.lowcode.annotation.CrudController;
import cn.geminis.lowcode.compiler.code.*;
import cn.geminis.lowcode.utils.Flags;
import cn.geminis.lowcode.utils.Utils;
import com.google.auto.service.AutoService;

import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.TypeElement;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author puddi
 */
@AutoService(Processor.class)
@SupportedAnnotationTypes("cn.geminis.lowcode.annotation.CrudController")
public class CrudControllerProcessor extends AbstractBaseProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        roundEnv.getElementsAnnotatedWith(CrudController.class)
                .forEach(element -> {
                    var entityQualifiedName = Utils.getElementQualifiedName(element);
                    var entityPackageName = Utils.getElementPackageName(element);
                    var parentPackageName = Utils.removeLastQualifiedName(entityPackageName);
                    var controllerPackageName = parentPackageName + ".controller";
                    var controllerName = element.getSimpleName() + "Controller";

                    var controller = new LcClass(
                            LcPackage.ensure(controllerPackageName),
                            new LcModifier(
                                    Flags.PUBLIC,
                                    new LcAnnotation("org.springframework.web.bind.annotation.RestController"),
                                    new LcAnnotation(
                                            "org.springframework.web.bind.annotation.RequestMapping",
                                            Map.of(
                                                    "", element.getSimpleName().toString().toLowerCase()
                                            )
                                    )
                            ),
                            controllerName);

                    addRepositoryField(controller);
                    addConstructor(controller);
                    addFindPageMethod(controller, entityQualifiedName);
                    addFindOneMethod(controller, entityQualifiedName);
                    addSaveMethod(controller, entityQualifiedName);
                    addDeleteMethod(controller, entityQualifiedName);

//                    controller.generateClassFile();
                    controller.generateSourceFile();

                    Utils.noteMessage(controllerPackageName + "." + controllerName + " Generated");
                });

        return true;
    }

    private void addConstructor(LcClass controller) {
        var constructor = new LcConstructor(
                new LcModifier(Flags.PUBLIC),
                List.of(
                        new LcVariable(
                                new LcModifier(Flags.FINAL),
                                "cn.geminis.data.jpa.GeminiRepository",
                                "repository"
                        )
                ),
                List.of(
                        new LcAssign("this.repository", new LcIdent("repository")).exec()
                )
        );
        controller.append(constructor);
    }

    private void addRepositoryField(LcClass controller) {
        var repositoryField = new LcVariable(
                new LcModifier(Flags.PRIVATE | Flags.FINAL),
                "cn.geminis.data.jpa.GeminiRepository",
                "repository"
        );
        controller.append(repositoryField);
    }

    private void addFindPageMethod(LcClass controller, String entityName) {
        var method = new LcMethod(
                new LcModifier(
                        Flags.PUBLIC,
                        new LcAnnotation("org.springframework.web.bind.annotation.GetMapping")
                ),
                "org.springframework.data.domain.Page<" + entityName + ">",
                "findPage",
                List.of(
                        new LcVariable(
                                new LcModifier(Flags.PARAMETER),
                                "cn.geminis.core.data.query.QueryParameters",
                                "queryParameters"
                        )
                ),
                List.of(),
                List.of(
                        new LcApply(
                                "this.repository.findPage",
                                new LcIdent(entityName + ".class"),
                                new LcIdent("queryParameters")
                        ).rtn()
                )
        );
        controller.append(method);
    }

    private void addFindOneMethod(LcClass controller, String entityName) {
        var method = new LcMethod(
                new LcModifier(
                        Flags.PUBLIC,
                        new LcAnnotation(
                                "org.springframework.web.bind.annotation.GetMapping",
                                Map.of("", "{id}")
                        )
                ),
                entityName,
                "findOne",
                List.of(
                        new LcVariable(
                                new LcModifier(Flags.PARAMETER),
                                "java.lang.Object",
                                "id"
                        )
                ),
                List.of(),
                List.of(
                        new LcApply(
                                List.of(
                                        new LcApply.InvokeInfo(
                                                "this.repository.findOne",
                                                new LcIdent(entityName + ".class"),
                                                new LcIdent("id")
                                        ),
                                        new LcApply.InvokeInfo("get")
                                )
                        ).rtn()
                )
        );
        controller.append(method);
    }

    private void addSaveMethod(LcClass controller, String entityName) {
        var method = new LcMethod(
                new LcModifier(
                        Flags.PUBLIC,
                        new LcAnnotation("org.springframework.web.bind.annotation.PostMapping")
                ),
                "VOID",
                "save",
                List.of(
                        new LcVariable(
                                new LcModifier(Flags.PARAMETER),
                                entityName,
                                "obj"
                        )
                ),
                List.of(),
                List.of(
                        new LcApply("this.repository.save", new LcIdent("obj")).exec()
                )
        );
        controller.append(method);
    }

    private void addDeleteMethod(LcClass controller, String entityName) {
        var method = new LcMethod(
                new LcModifier(
                        Flags.PUBLIC,
                        new LcAnnotation("org.springframework.web.bind.annotation.DeleteMapping")
                ),
                "VOID",
                "delete",
                List.of(
                        new LcVariable(
                                new LcModifier(Flags.PARAMETER),
                                "java.util.List<java.lang.String>",
                                "ids"
                        )
                ),
                List.of(),
                List.of(
                        new LcApply(
                                "ids.forEach",
                                new LcLambda(
                                        List.of(
                                                new LcVariable(
                                                        new LcModifier(Flags.PARAMETER),
                                                        "java.lang.String",
                                                        "id"
                                                )
                                        ),
                                        new LcApply(
                                                "this.repository.deleteById",
                                                new LcIdent(entityName + ".class"),
                                                new LcIdent("id")
                                        ).exec()
                                )
                        ).exec()
                )
        );
        controller.append(method);
    }
}
